Skip to main content

第 5 章:在容器與主機中管理資料

資料卷 (Data volumes)

資料卷是一個可供一個或多個容器使用的特殊目錄,它繞過 UFS,可以提供很多有用的特性

  • 資料卷可以在容器之間共享和重用
  • 對資料卷的修改會立即生效
  • 對資料卷的更新,不會影響映像檔
  • 卷會一直存在,直到沒有容器使用
  • 資料卷的使用,類似於 Linux 下對目錄或檔案進行 mount。

建立一個資料卷

在用 docker run 命令的時候,使用 -v 標記來建立一個資料卷並掛載到容器裡。在一次 run 中多次使用可以掛載多個資料卷。 下面建立一個 web 容器,並載入一個資料卷到容器的 /webapp 目錄。

sudo docker run -d -P --name web -v /webapp training/webapp python app.py
  • 注意:也可以在 Dockerfile 中使用 VOLUME 來新增一個或者多個新的卷到由該映像檔建立的任意容器。

掛載一個主機目錄作為資料卷

使用 -v 標記也可以指定掛載一個本地主機的目錄到容器中去。

sudo docker run -d -P --name web -v /src/webapp:/opt/webapp training/webapp python app.py

上面的命令載入主機的 /src/webapp 目錄到容器的 /opt/webapp 目錄。這個功能在進行測試的時候十分方便,比如使用者可以放置一些程式到本地目錄中,來查看容器是否正常工作。本地目錄的路徑必須是絕對路徑,如果目錄不存在 Docker 會自動為你建立它。

  • 注意:Dockerfile 中不支援這種用法,這是因為 Dockerfile 是為了移植和分享用的。然而,不同作業系統的路徑格式不一樣,所以目前還不能支援。

Docker 掛載資料卷的預設權限是讀寫,使用者也可以透過 :ro 指定為唯讀。

sudo docker run -d -P --name web -v /src/webapp:/opt/webapp:ro
training/webapp python app.py

加了 :ro 之後,就掛載為唯讀了。

掛載一個本地主機檔案作為資料卷

  • v 標記也可以從主機掛載單個檔案到容器中。
sudo docker run --rm -it -v ~/.bash_history:/.bash_history ubuntu /bin/bash

這樣就可以記錄在容器輸入過的命令了。

  • 注意:如果直接掛載一個檔案,很多檔案編輯工具,包括 vi 或者 sed --in-place,可能會造成檔案 inode 的改變,從 Docker 1.1 .0起,這會導致報錯誤訊息。所以最簡單的辦法就直接掛載檔案的父目錄。

之前有說明過在執行 Docker Container 的時侯檔案系統會分為 Image 層、Init 層以及可讀可寫層這三個部份,當 Docker Container 刪除掉之後,存放在 Docker Container 上的資料也就會跟著刪除掉,因此需要把不想要被刪除掉的資料存放在實體機器上,避免資料不見的問題。

主要實作如何使用 Docker 的 Volume 功能,把資料寫入到實體機器上,主要的方式有二個方法:

  1. 在執行 docker run 指令時加上 v 參數,使得 Container 裡面的檔案路徑Mapping 到實體主機的檔案路徑。
  2. 在撰寫 Dockerfile 時,加入 VOLUME 指令,做到把資料存放在實體的主機上。使用這種方法需要搭配 docker inspect 指令,才能查詢到實體主機檔案的存放路徑在哪。(這個明天會做介紹)
  • 範例

在使用 docker run 指令時,指定 -v 參數,使得實體主機的資料夾路徑 Mapping 到 Container 的資料夾路徑,指令

docker run -it -v /home/user1/storage:/storage centos /bin/bash

/home/user1/storage 是實體主機的資料夾路徑 Mapping 到 Container 裡面的 /storage 資料夾路徑。

我們就會在上個畫面中的左邊視窗看到執行 docker run 指令時使用 -v參數 Mapping 到實體主機的 /home/user1/storage 的資料夾。然後在右邊視窗中的實體主機 /home/user1/storage 的資料夾建立一個 helloworld.txt 的檔案,最後再回到左邊視窗的 Container 裡面的 /storage 資料夾去看 helloworld.txt 的檔案已經存在了。

  • 範例

在使用 docker run 指令時,指定 -v參數,但是沒有指定實體主機的檔案路徑,指令

docker run -it -v /storage centos /bin/bash

如果要找到在實體主機真實的資料夾路徑

docker inspect -f '{{.Mounts}}' 4c2a9ef663c2

4c2a9ef663c2 是 Container 的 ID

上圖就可以找到 Volume 在實體主機的真實路徑,這時侯就可以使用 cd command 切進上圖查詢出來的資料夾路徑並且寫入一個檔案在此資料夾,但是要注意使用者權限的問題,這裡是直接切換成 root 權限

Example 1:如何讓 Container 之間的資料共享

撰寫一個 Dockerfile 使用 VOLUME 指令,把 Docker 的 Image Build 起來,然後啟動 Docker Container,把資料寫進在 Docker Container 裡面,最後使用 docker inspect 指令,找到 Mapping 到實體主機的資料夾路徑,確認是否有看到之前寫在 Container 裡面的檔案。

  1. Dockerfile
FROM centos
VOLUME ["/storage"]

另外在 VOLUME 指令的寫法可以寫成多個路徑

VOLUME ["/storage1", "/storage2", "/storage2"]
docker build -t volumetest .

輸入 docker build 指令時,資料夾要切換到和 Dockerfile 檔案同一層的資料夾路徑

docker images

啟動 Docker Container

docker run -it volumetest /bin/bash

因為 VOLUME 已經寫在 Dockerfile 裡,所以在使用 docker run 指令時沒有給 -v 參數

  1. 在 Container 裡面寫入一個檔案
  2. 在實體主機上使用 docker inspect 指令,找到 Volume 在實體主機的資料夾路徑

e2987aaab700 為 ContainerID,也可以使用指定 Container Name 的方式

  1. 使用 root 權限,切換到 Volume 的實體主機的資料夾路徑之後,可以看到在第4步驟在 Container 裡面寫的檔案,也可以在實體主機的資料夾看到 helloworld.txt 的檔案

Example 2:如何讓 Container 之間的資料共享

  1. 啟動第一個 Container 指令
docker run -it -v /data--name=container1 centos /bin/bash
  1. 啟動第二個 Container 指令
docker run -it --volumes-from container1 --name=container2 centos /bin/bash
  • -volumes-from 參數指定 Container Name 為 Container1 的 Volume 資料和Container2 做共享
  1. 測試二個 Container 之間資料是否能共享,畫面

左邊的 Container1 切換到 /data 資料夾之後建立一個 helloworld.txt 的檔案,之後在右邊的 Container2 切換到 /data 資料夾之後查看 helloworld.txt 的檔案內容為 HELLOWORLD,這樣就代表了 Container 之間的資料有做到共享的效果。

今天介紹了 Dockerfile Volume 的用法以及 Container 之間的資料做共享,不用另外的架設 file server 就可以分享資料,使資料在使用上更加的方便,對於在備份資料方面也更加的簡單。

資料卷容器 (Data volume containers)

如果你有一些持續更新的資料需要在容器之間共享,最好建立資料卷容器。

資料卷容器,其實就是一個正常的容器,專門用來提供資料卷供其它容器掛載的。

首先,建立一個命名的資料卷容器 dbdata:

sudo docker run -d -v /dbdata --name dbdata training/postgres echo Data-only container for postgres

然後,在其他容器中使用 --volumes-from 來掛載 dbdata 容器中的資料卷。

sudo docker run -d --volumes-from dbdata --name db1 training/postgres

sudo docker run -d --volumes-from dbdata --name db2 training/postgres

還可以使用多個 --volumes-from 參數來從多個容器掛載多個資料卷。 也可以從其他已經掛載了容器卷的容器來掛載資料卷。

sudo docker run -d --name db3 --volumes-from db1 training/postgres
  • 注意:使用 -volumes-from 參數所掛載資料卷的容器自己並不需要保持在執行狀態。

如果刪除了掛載的容器(包括 dbdata、db1 和 db2),資料卷並不會被自動刪除。如果要刪除一個資料卷,必須在刪除最後一個還掛載著它的容器時使用 docker rm -v 命令來指定同時刪除關聯的容器。 這可以讓使用者在容器之間升級和移動資料卷。具體的操作將在下一節中進行講解。

備份資料卷

首先使用 --volumes-from 標記來建立一個載入 dbdata 容器卷的容器,並從本地主機掛載當前到容器的 /backup 目錄。命令以下:

sudo docker run --volumes-from dbdata -v $(pwd):/backup ubuntu tar cvf /backup/backup.tar /dbdata

容器啟動後,使用了 tar 命令來將 dbdata 卷備份為本地的 /backup/backup.tar

恢復資料卷

如果要恢復資料到一個容器,首先建立一個帶有資料卷的容器 dbdata2。

sudo docker run -v /dbdata --name dbdata2 ubuntu /bin/bash

然後建立另一個容器,掛載 dbdata2 的容器,並使用 untar 解壓備份檔案到掛載的容器卷中。

sudo docker run --volumes-from dbdata2 -v $(pwd):/backup busybox tar xvf

/backup/backup.tar

使用 Docker 的 Volume 來部署 war 檔

我們在開發的過程中直接把 war 檔包進去 Docker Image 之後,如果發現程式需要修改,那改完之後就需要重新 Build Image 以及重新啟動 Container,這樣有點麻煩,如果能夠把放 war 檔的資料夾共享在實體主機上的資料夾,這樣只要把改完的 war 檔重新的放在實體主機上的資料夾,就可以不用重 Build Image 以及重新啟動 Container。

  • Dockerfile
FROM java
MAINTAINER jack
RUN apt-get update && apt-get install -y wget
RUN cd /
RUN wget http://apache.stu.edu.tw/tomcat/tomcat-7/v7.0.82/bin/apache-tomcat-7.0.82.tar.gz
RUN tar zxvf apache-tomcat-7.0.82.tar.gz
VOLUME ["/apache-tomcat-7.0.82/webapps"]
CMD ["/apache-tomcat-7.0.82/bin/catalina.sh", "run"]這裡使用的 Base Image 修改成 java 這樣就不用下載 JDK 以及設定 JAVA_HOME 的環境變數,另外在 Dockerfile 使用 VOLUME 指令,把放 war 的資料夾可以分享在實體主機的資料夾裡。
  • Build Docker Image
docker build -t tomcatserver .

這裡指定的 Image Name 為 tomcatserver

  • 使用 Build 完成的 Docker Image,執行 Docker container
docker run -d --name tomcat -p 8080:8080 tomcatserver
  • 使用 docker inspect 指令,找到實體資料夾的路徑
docker inspect -f '{{.Mounts}}' tomcat
  • 查詢檔案路徑後就可以下載 Jenskin 的 war 檔,放到實體資料夾的路徑裡
  • 使用 Browser 連到 URL http://10.1.3.243:8080/jenkins,IP 為實體主機的 IP
  • 看到以上的畫面需要輸入 Jenkins 認證的密碼,所以需要執行 docker exec 指令把 Container 上的密碼印出來
docker exec tomcat cat /root/.jenkins/secrets/initialAdminPassword

docker exec 後面的 tomcat 是 Container Name,也可以使用 ContainerID 的方式